﻿#include "MyCap.h"
#include "OCMain/Timer.h"
#include "OCMain/Date.h"
#include <iomanip>
#include <drogon/drogon.h>
#include "OCVolume/Swan3.h"
#include "OCMain/ZipUtil.h"

using namespace OCPP;
using namespace OCPP::Volume;

const long SeceondsPerMinute = 60;
const long SeceondsPerHour = 3600;
const String dbName = "CAP3D";
const int lodZ = 3;

template<> MyCap* CSingleton<MyCap>::msSingleton = 0;

inline bool removeDir(String dir)
{
	StringVector files = FileUtil::findFiles(dir);

	for (auto file : files)
	{
		if (file == "." || file == "..") continue;
		String fileName = joinPath(dir, file);
		if (FileUtil::typeOfFile(fileName) == FileUtil::REGULAR_FILE)
		{
			FileUtil::deleteFile(fileName);
		}
		else
		{
			removeDir(fileName);
		}
	}

	return FileUtil::deletFolder(dir);
}

inline String formatTileCacheName(int z, int x0, int y0, int x1, int y1)
{
	return str_format("%d_%d_%d_%d_%d.bin.gz", z, x0, y0, x1, y1);
}

inline String formatDbDateStr(Date date)
{
	return date.toCustomedFormattedStringLocal("%Y-%m-%d %H:%M:%S", false);
}

inline String formatDbDateStr(String date, String time)
{
	return date.substr(0, 4) + "-" + date.substr(4, 2) + "-"
		+ date.substr(6, 2) + " " + time.substr(0, 2)
		+ ":" + time.substr(2, 2)
		+ ":" + time.substr(4, 2);
}

inline String formatPathDateStr(Date date)
{
	return date.toCustomedFormattedStringLocal("%Y%m%d", false);
}


inline String formatFileDateStr(Date date)
{
	return date.toCustomedFormattedStringLocal("%Y%m%d%H%M%S", false);
}

String MyCap::get(String time, String product)
{
	if (time.length() != 19) return "";
	String datePath = formatFileDateStr(Date::fromDbStringLocal(time));
	String fileName = joinPath(_outputPath, datePath.substr(0, 8), datePath, product + ".bin.gz");
	return FileUtil::getFileBinary(fileName);
}

String MyCap::getCAP(String time, int z, int x0, int y0, int x1, int y1)
{
	if (time.length() != 19) return "";
	String datePath = formatFileDateStr(Date::fromDbStringLocal(time));
	String name = formatTileCacheName(z, x0, y0, x1, y1);
	String fileName = joinPath(_outputPath, datePath.substr(0, 8), datePath, name);
	return FileUtil::getFileBinary(fileName);
}

String MyCap::get3(String time, int z, int x0, int y0, int x1, int y1)
{
	if (time.length() != 19) return "";
	String datePath = formatFileDateStr(Date::fromDbStringLocal(time));

	//AutoTimer timer;
	String content;

	String path = joinPath(_outputPath, datePath.substr(0, 8), datePath);

	int nx = x1 - x0 + 1;
	int ny = y1 - y0 + 1;

	int tileWidth = 6200 / pow(2, z);
	int tileHeight = 4200 / pow(2, z);
	//size_t rowBytes = tileWidth * 3;
	size_t rowBytes = tileWidth * 1;
	size_t outRowBytes = nx * rowBytes;

	for (int i = 0; i < 24; i++)
	{
		StringStream sstream;
		sstream << std::setw(2) << std::setfill('0') << i;

		for (int y = y0; y <= y1; y++)
		{
			for (int x = x0; x <= x1; x++)
			{
				String fileName = joinPath(path, sstream.str(), str_format("%d_%d_%d.bin", z, y, x));
				String bin = FileUtil::getFileBinary(fileName);
				if (content.empty())
				{
					content.resize(bin.size() * 24 * nx * ny);
				}
				int ix = x - x0;
				int iy = y - y0;
				int io = i * nx * ny + ix + iy * nx;

				size_t zOffset = bin.size() * i * nx * ny;
				size_t yOffset = iy * bin.size();

				for (int h = 0; h < tileHeight; h++)
				{
					memcpy(content.data() + zOffset + ix * rowBytes + (h + iy * tileHeight) * outRowBytes,
						bin.data() + h * rowBytes,
						rowBytes);
				}
			}
		}
	}

	return content;
}

// T_RADA_C_BABJ_20241211021805_P_DOR_ACHN_20241211_101500_CAP_10.bin 不支持
// T_RADA_C_BABJ_20250512104505_P_DOR_ACHN_CAP_20250512_104200_15.bin //2025 05 13增加判断
// 
// Z_RADA_C_BABJ_20240720000119_P_DOR_ACHN_CAP_20240719_235400_00.bin
inline static const size_t templateFileNameLength = 66;

std::tuple<String, String, String> parseName(String name)
{
	name = name.substr(0, name.find_last_of('.'));
	String date, time, height;
	StringVector sv = StringUtil::split(name, "_");
	if (sv.size() == 12 && name[0] == 'Z')
	{
		date = sv[9];
		time = sv[10];
		height = sv[11];
	}
	return std::make_tuple(date, time, height);
}

inline CAPFileVec findBin(String path, String fileExt)
{
	CAPFileVec vec;
	std::map<String, std::set<String> > dateTimeMap;
	StringVector files = FileUtil::findFiles(path);
	for (auto file : files)
	{
		if (file.length() == templateFileNameLength)
		{
			const auto [date, time, height] = parseName(file);
			String dateTime = date + time;
			dateTimeMap[dateTime].insert(height);
			if (dateTimeMap[dateTime].size() == 24)
			{
				vec.push_back({
					date, time, file.substr(0,file.find_last_of('_'))
					});
			}
		}
	}
	return vec;
}
void MyCap::init()
{
	if (!FileUtil::existFile(_outputPath))
	{
		FileUtil::makeDirectory(_outputPath);
	}

	String fileName = joinPath(_outputPath, dbName + ".db");
	drogon::app().createDbClient("sqlite3", "", 0, "", "", "", 1, fileName, dbName);
	drogon::app().getLoop()->runAfter(0, [] {
		drogon::orm::DbClientPtr dbClient = drogon::app().getDbClient(dbName);
		if (dbClient)
		{
			try {
				String sql1 = "CREATE TABLE IF NOT EXISTS datetime(\
				id INTEGER PRIMARY KEY,\
				timestamp TIMESTAMP DEFAULT(DATETIME('now', 'localtime')))";
				dbClient->execSqlSync(sql1);
				//dbClient->execSqlSync("DELETE FROM datetime");
			}
			catch (drogon::orm::DrogonDbException& ex) {
				LOG_ERROR << ex.base().what() << "\n";
			}
			catch (...) {
				LOG_ERROR << "unknow error.\n";
				return;
			}
		}
		});
}

std::set<String> MyCap::query(String startTime, String endTime)
{
	std::set<String> res;

	drogon::orm::DbClientPtr dbClient = drogon::app().getDbClient(dbName);
	if (dbClient)
	{
		try {
			const drogon::orm::Result result = dbClient->execSqlSync(
				"select * from datetime where timestamp>=$1 and timestamp<=$2"
				, startTime
				, endTime
			);
			for (auto row : result)
			{
				res.insert(row["timestamp"].as<std::string>());
			}
		}
		catch (drogon::orm::DrogonDbException& ex) {
			LOG_ERROR << ex.base().what() << "\n";
		}
		catch (...) {
			LOG_ERROR << "unknow error.\n";
		}
	}

	return res;
}


int MyCap::insert(String dateTime)
{
	drogon::orm::DbClientPtr dbClient = drogon::app().getDbClient(dbName);
	if (dbClient)
	{
		try {
			const drogon::orm::Result result = dbClient->execSqlSync(
				"INSERT INTO datetime (timestamp) VALUES($1)"
				, dateTime
			);
			return result.affectedRows();
		}
		catch (drogon::orm::DrogonDbException& ex) {
			LOG_ERROR << ex.base().what() << "\n";
		}
		catch (...) {
			LOG_ERROR << "unknow error.\n";
		}
	}
	return 0;
}

inline void remove(Date date)
{
	String dateTime = formatDbDateStr(date);
	drogon::orm::DbClientPtr dbClient = drogon::app().getDbClient(dbName);
	if (dbClient)
	{
		try {
			dbClient->execSqlSync(
				"DELETE FROM datetime WHERE timestamp < $1"
				, dateTime
			);
		}
		catch (drogon::orm::DrogonDbException& ex) {
			LOG_ERROR << ex.base().what() << "\n";
		}
		catch (...) {
			LOG_ERROR << "unknow error.\n";
		}
	}
}

bool MyCap::convert(const CAPFile& file, String dataPath, String ouputPath)
{
	String dateTime = file.date + file.time;
	String dateOutputPath = joinPath(ouputPath, dateTime);
	if (!FileUtil::existFile(dateOutputPath))
	{
		FileUtil::makeDirectory(dateOutputPath);
	}

	//check file all level exist
	std::vector<std::tuple<String, String> > dataFiles;

	for (int i = 0; i < 24; i++) {
		StringStream sstream;
		sstream << std::setw(2) << std::setfill('0') << i;

		String height = sstream.str();
		String dataFileName = joinPath(dataPath, file.file + "_" + height + ".bin");
		if (!FileUtil::existFile(dataFileName))
		{
			OCPP_LOG_ERROR << "File not exist " << dataFileName << "\n";
			break;
		}
		dataFiles.push_back({ height,dataFileName });
	}

	if (dataFiles.size() == 24)
	{
		{
			Uint8Vector crefData;
			for (const auto& [height, fileName] : dataFiles) {
				String tileOutputPath = joinPath(dateOutputPath, height);
				FileUtil::makeDirectory(tileOutputPath);
				Swan3 reader;
				reader.splitLOD(fileName, tileOutputPath, crefData);

				if (height == "00")
				{
					std::ofstream ofs(joinPath(dateOutputPath, "00.bin.gz"), std::ios::binary);
					String gzData = ZipUtil::gzipCompress((char*)&crefData[0], crefData.size());
					ofs.write(gzData.data(), gzData.size());
				}
			}
			std::ofstream ofs(joinPath(dateOutputPath, "cref.bin.gz"), std::ios::binary);
			String gzData = ZipUtil::gzipCompress((char*)&crefData[0], crefData.size());
			ofs.write(gzData.data(), gzData.size());
		}

		int z = lodZ;
		int tileNum = pow(2, z);
		for (int r = 0; r < tileNum - 1; r++)
		{
			for (int c = 0; c < tileNum - 1; c++)
			{
				String data = get3(formatDbDateStr(file.date, file.time), z, c, r, c + 1, r + 1);
				String name = formatTileCacheName(z, c, r, c + 1, r + 1);
				String gzData = ZipUtil::gzipCompress(data);
				std::ofstream ofs(joinPath(dateOutputPath, name), std::ios::binary);
				ofs.write((char*)&gzData[0], gzData.size() * sizeof(uint8));
			}
		}

		for (const auto& [height, fileName] : dataFiles) {
			String tileOutputPath = joinPath(dateOutputPath, height);
			removeDir(tileOutputPath);
		}

		return true;
	}


	return false;
}

void MyCap::clean()
{
	Date date = Date::now().after(-Date::timezoneOffset() - 10 * 24 * SeceondsPerHour).roundSecond();
	remove(date);
	for (int i = 0; i < 9; i++)
	{
		Date date1 = date.after(i * 24 * SeceondsPerHour);
		String dateStr = formatPathDateStr(date1);
		String ouputPath = joinPath(_outputPath, dateStr);
		if (!removeDir(ouputPath)) break;
		OCPP_LOG_INFO << "remove dir:" << ouputPath << "\n";
	}
}

void MyCap::doJob()
{
	Date date = Date::now().after(-Date::timezoneOffset()).roundSecond();

	if (!fakeDate.empty())
	{
		date = Date::fromDbStringLocal(fakeDate);
	}

	String startTime = formatDbDateStr(date.roundDay().after(-8 * SeceondsPerHour));
	String endTime = formatDbDateStr(date);
	OCPP_LOG_DEBUG << "CAP process " << startTime << " to " << endTime << ".\n";

	String dateStr = formatPathDateStr(date);
	String dataPath = joinPath(_dataPath, dateStr.substr(0, 4), dateStr, "CAP");
	String ouputPath = joinPath(_outputPath, dateStr);

	auto dateTimeSet = query(startTime, endTime);

	auto fileVec = findBin(dataPath, "bin");

	std::vector<std::future<void> > tasks;
	for (const auto& capFile : fileVec)
	{
		String dateTime = formatDbDateStr(capFile.date, capFile.time);

		if (dateTimeSet.find(dateTime) == dateTimeSet.end())
		{
			// 2025/1/6 修正重复处理
			dateTimeSet.insert(dateTime);

			auto doWork = [&dataPath, &ouputPath, this](CAPFile capFile, String dateTime)
				{
					OCPP_LOG_DEBUG << "New:" << dateTime << "\n";
					if (convert(capFile, dataPath, ouputPath))
					{
						OCPP_LOG_DEBUG << "Done:" << dateTime << "\n";
						insert(dateTime);
					}
				};

			tasks.push_back(std::async(doWork, capFile, dateTime));

			if (tasks.size() == _threadNum)
			{
				for (auto& f : tasks)
				{
					f.get();
				}
				tasks.clear();
			}
		}
	}

	for (auto& f : tasks)
	{
		f.get();
	}
	tasks.clear();
}

void MyCap::startTimer()
{
	init();

	static std::thread t;

	drogon::app().getLoop()->runAfter(0, [this] {
		t = std::thread([this]() {
			uint32 count = 0;
			while (true)
			{
				if (_expiredTime > 0 && count++ % (24 * 10 * _expiredTime) /*10天*/ == 0)
				{
					count = 0;
					this->clean();
				}
				Timer countTimer(true);
				this->doJob();
				if (interval < 0)
				{	
					break;
				}
				long elapse = countTimer.GetTimeMS() * 0.001;
				long sleepSec = interval * SeceondsPerMinute - elapse;
				if (sleepSec > 0) {
					std::this_thread::sleep_for(std::chrono::seconds(sleepSec));
				}
			}
			});
		t.detach();
		});


}